﻿namespace Sgr.BillNumbering.Services.Impl
{
    /// <summary>
    /// 默认编码生成策略实现
    /// </summary>
    /// <remarks>
    /// 实现基础的编码生成逻辑，支持以下功能：
    /// <list type="bullet">
    /// <item><description>业务编码前缀和类型代码</description></item>
    /// <item><description>日期格式化</description></item>
    /// <item><description>序列号生成和格式化</description></item>
    /// <item><description>校验位计算</description></item>
    /// <item><description>序列重置策略（按天、周、月、年）</description></item>
    /// <item><description>序列溢出处理</description></item>
    /// </list>
    /// </remarks>
    public class DefaultCodeGenerationStrategy : ICodeGenerationStrategy
    {
        /// <summary>
        /// 策略名称
        /// </summary>
        public string StrategyName => "Default";

        /// <summary>
        /// 异步生成编码
        /// </summary>
        /// <param name="rule">编码规则配置</param>
        /// <param name="sequenceGenerator">序列号生成器</param>
        /// <param name="customPrefix">自定义前缀</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>生成的业务编码字符串</returns>
        /// <exception cref="ArgumentNullException">当参数为空时抛出</exception>
        /// <exception cref="InvalidOperationException">当序列号超出范围时抛出</exception>
        public async Task<string> GenerateAsync(CodeRule rule, ICodeSequence sequenceGenerator, string customPrefix = "", CancellationToken cancellationToken = default)
        {
            var sequenceValue = await sequenceGenerator.GetNextValueAsync(rule.BusinessCode, customPrefix, rule.ResetStrategy, rule.SegmentSize, cancellationToken);

            return GenerateCode(rule, sequenceValue, customPrefix);
        }

        /// <summary>
        /// 根据编码规则生成最终的编码字符串
        /// </summary>
        /// <param name="rule">编码规则</param>
        /// <param name="sequenceValue">序列号值</param>
        /// <param name="customPrefix">自定义前缀</param>
        /// <returns>完整的业务编码字符串</returns>
        /// <remarks>
        /// 编码组成规则（按顺序拼接）：
        /// 1. 业务编码前缀（如果配置了 BusinessCode）
        /// 2. 业务类型代码（如果配置了 BusinessTypeCode）
        /// 3. 日期部分（如果配置了 DateFormat）
        /// 4. 序列号（必需，根据 SequenceLength 左补零）
        /// 5. 校验位（如果 IncludeCheckDigit 为 true）
        /// 各部分之间使用 Separator 连接
        /// </remarks>
        private string GenerateCode(CodeRule rule, long sequenceValue, string customPrefix = "")
        {
            var parts = new List<string>();

            // 业务编码前缀（如：SO、PO、INV 等）
            if (!string.IsNullOrEmpty(rule.BusinessCode))
            {
                parts.Add(rule.BusinessCode);
            }

            // 业务类型代码（如：STD、URG、RET 等，用于进一步细分业务类型）
            if (!string.IsNullOrEmpty(rule.BusinessTypeCode))
            {
                parts.Add(rule.BusinessTypeCode);
            }

            //// 自定义前缀（可用于特殊标识）
            //if (!string.IsNullOrEmpty(customPrefix))
            //{
            //    parts.Add(customPrefix);
            //}

            // 日期部分（按配置的格式添加当前日期）
            if (rule.ResetStrategy == SequenceResetStrategy.Daily
                || rule.ResetStrategy == SequenceResetStrategy.Week
                || rule.ResetStrategy == SequenceResetStrategy.Monthly
                || rule.ResetStrategy == SequenceResetStrategy.Yearly)
                parts.Add(TimeKeyGenerator.Generate(rule.ResetStrategy));

            // 序列号部分（格式化为指定长度，左补零
            var sequenceStr = FormatSequenceNumber(sequenceValue, rule);
            parts.Add(sequenceStr);

            // 校验位（用于验证编码完整性，防止录入错误）
            if (rule.IncludeCheckDigit)
            {
                parts.Add(CalculateCheckDigit(string.Join(rule.Separator, parts)));
            }

            return string.Join(rule.Separator, parts);
        }

        /// <summary>
        /// 格式化序列号，处理超范围情况
        /// </summary>
        /// <param name="sequenceValue">原始序列号值</param>
        /// <param name="rule">编码规则（包含长度和溢出策略配置）</param>
        /// <returns>格式化后的序列号字符串</returns>
        /// <exception cref="ArgumentException">当序列长度配置无效时抛出</exception>
        /// <exception cref="InvalidOperationException">当序列号超出范围且策略为抛出异常时抛出</exception>
        /// <exception cref="NotSupportedException">当使用了不支持的溢出策略时抛出</exception>
        /// <remarks>
        /// 溢出处理策略：
        /// <list type="bullet">
        /// <item><description>ThrowException: 抛出异常，中断流程</description></item>
        /// <item><description>Extend: 扩展长度，不限制位数</description></item>
        /// </list>
        /// </remarks>
        private string FormatSequenceNumber(long sequenceValue, CodeRule rule)
        {
            int sequenceLength = rule.SequenceLength;
            if (sequenceLength <= 0)
                throw new ArgumentException("序列长度必须大于0", nameof(sequenceLength));

            var maxValue = (long)Math.Pow(10, sequenceLength) - 1;

            if (sequenceValue <= maxValue)
            {
                // 正常情况
                return sequenceValue.ToString().PadLeft(sequenceLength, '0');
            }

            // 超范围处理 - 可通过配置选择策略
            var overflowStrategy = rule.SequenceOverflow;

            return overflowStrategy switch
            {
                SequenceOverflowStrategy.ThrowException =>
                    throw new InvalidOperationException($"序列号 {sequenceValue} 超出最大范围 {maxValue}"),

                SequenceOverflowStrategy.Extend =>
                    sequenceValue.ToString(), // 不限制长度

                _ => throw new NotSupportedException($"不支持的溢出策略: {overflowStrategy}")
            };
        }

        /// <summary>
        /// 计算校验位
        /// </summary>
        /// <param name="code">待计算校验位的编码字符串</param>
        /// <returns>单位数字的校验位</returns>
        /// <remarks>
        /// 使用简单的字符ASCII值求和取模算法：
        /// 1. 将编码字符串中每个字符的ASCII值相加
        /// 2. 对10取模得到0-9的校验位
        /// 注意：这是简化实现，实际项目中可能需要使用更复杂的校验算法（如Luhn算法）
        /// </remarks>
        private string CalculateCheckDigit(string code)
        {
            // 简单实现，可根据实际需求调整
            int sum = code.Sum(c => c);
            return (sum % 10).ToString();
        }
    }
}